﻿using System;
using System.Drawing;
using System.Windows.Forms;

using Storm.TextEditor.Win32;
using Storm.TextEditor.Win32.Enums;
using Storm.TextEditor.Win32.Structs;

namespace Storm.TextEditor.Drawing.GDI
{
	public class GDISurface
		: GDIObject
	{
		#region Fields

		protected int width   = 0;
		protected int height  = 0;
		protected int tabSize = 4;

		protected IntPtr oldFont  = IntPtr.Zero;
		protected IntPtr oldPen   = IntPtr.Zero;
		protected IntPtr oldBrush = IntPtr.Zero;
		protected IntPtr oldBmp   = IntPtr.Zero;

		protected IntPtr hDC  = IntPtr.Zero;
		protected IntPtr hBMP = IntPtr.Zero;

		private WeakReference control = null;

		#endregion

		#region Properties

		private Control Control
		{
			get
			{
				if (control != null)
					return (Control)control.Target;
				else
					return null;
			}
			set { control = new WeakReference(value); }
		}

		public GDIFont Font
		{
			get
			{
				GDITextMetric tm = new GDITextMetric();
				string fontname = "                                                ";

				NativeGdi32Api.GetTextMetrics(hDC, ref tm);
				NativeGdi32Api.GetTextFace(hDC, 79, fontname);

				GDIFont gf = new GDIFont();
				gf.fontname = fontname;
				gf.bold = (tm.tmWeight > 400);
				gf.italic = (tm.tmItalic != 0);
				gf.underline = (tm.tmUnderlined != 0);
				gf.strikethrough = (tm.tmStruckOut != 0);

				gf.size = (int)(((double)(tm.tmMemoryHeight) / (double)tm.tmDigitizedAspectY) * 72);
				return gf;
			}
			set
			{
				IntPtr res = NativeGdi32Api.SelectObject(hDC, value.hFont);
				if (oldFont == IntPtr.Zero)
					oldFont = res;
			}
		}

		public IntPtr HDC
		{
			get { return hDC; }
		}

		public IntPtr HBMP
		{
			get { return hBMP; }
		}

		public Color TextForeColor
		{
			get { return NativeUser32Api.IntToColor(NativeGdi32Api.GetTextColor(hDC)); }
			set { NativeGdi32Api.SetTextColor(hDC, NativeUser32Api.ColorToInt(value)); }
		}

		public Color TextBackColor
		{
			get { return NativeUser32Api.IntToColor(NativeGdi32Api.GetBkColor(hDC)); }
			set { NativeGdi32Api.SetBkColor(hDC, NativeUser32Api.ColorToInt(value)); }
		}

		public bool FontTransparent
		{
			get { return NativeGdi32Api.GetBkMode(hDC) < 2; }
			set { NativeGdi32Api.SetBkMode(hDC, value ? 1 : 2); }
		}

		#endregion

		#region Methods

		#region Protected

		protected void Init(int width, int height, IntPtr hdc)
		{
			this.width = width;
			this.height = height;

			hDC = NativeGdi32Api.CreateCompatibleDC(hdc);
			hBMP = NativeGdi32Api.CreateCompatibleBitmap(hdc, width, height);

			IntPtr ret = NativeGdi32Api.SelectObject(hDC, hBMP);
			oldBmp = ret;

			if (hDC == (IntPtr)0)
				throw new Exception("Creation of hDC failed.");

			if (hDC == (IntPtr)0)
				throw new Exception("Creation of hBMP failed.");
		}

		protected override void Destroy()
		{
			if (oldBmp != IntPtr.Zero)
				NativeGdi32Api.SelectObject(hDC, oldBmp);

			if (oldFont != IntPtr.Zero)
				NativeGdi32Api.SelectObject(hDC, oldFont);

			if (oldPen != IntPtr.Zero)
				NativeGdi32Api.SelectObject(hDC, oldPen);

			if (oldBrush != IntPtr.Zero)
				NativeGdi32Api.SelectObject(hDC, oldBrush);

			if (hBMP != (IntPtr)0)
				NativeGdi32Api.DeleteObject(hBMP);

			if (hDC != (IntPtr)0)
				NativeGdi32Api.DeleteDC(hDC);

			hBMP = (IntPtr)0;
			hDC = (IntPtr)0;

			base.Destroy();
		}

		#endregion

		#region Public

		public Size MeasureTabbedString(string text, int tabSize)
		{
			uint ret = NativeUser32Api.GetTabbedTextExtent(hDC, text, text.Length, 1, ref tabSize);
			return new Size((int)(ret & 0xFFFF), (int)((ret >> 16) & 0xFFFF));
		}

		public Size DrawTabbedString(string text, int x, int y, int tabOrigin, int tabSize)
		{
			int ret = NativeUser32Api.TabbedTextOut(hDC, x, y, text, text.Length, 2, ref tabSize, tabOrigin);
			return new Size(ret & 0xFFFF, (ret >> 16) & 0xFFFF);
		}

		public void RenderTo(IntPtr hdc, int x, int y)
		{
			NativeGdi32Api.BitBlt(hdc, x, y, width, height, hDC, 0, 0, (int)GDIRop.SrcCopy);
		}

		public void RenderTo(GDISurface target, int x, int y)
		{
			this.RenderTo(target.hDC, x, y);
		}

		public void RenderTo(GDISurface target, int sourceX, int sourceY, int width, int height, int destX, int destY)
		{
			NativeGdi32Api.BitBlt(target.hDC, destX, destY, width, height, hDC, sourceX, sourceY, (int)GDIRop.SrcCopy);
		}

		public void RenderToControl(int x, int y)
		{
			IntPtr hdc = NativeUser32Api.ControlDC(this.Control);

			this.RenderTo(hdc, x, y);
			NativeUser32Api.ReleaseDC(this.Control.Handle, hdc);
		}

		public Graphics CreateGraphics()
		{
			return Graphics.FromHdc(hDC);
		}

		public void FillRect(GDIBrush brush, int x, int y, int width, int height)
		{
			RECTAPI gr;
			gr.Top = y;
			gr.Left = x;
			gr.Right = width + x;
			gr.Bottom = height + y;

			NativeUser32Api.FillRect(hDC, ref gr, brush.brush);
		}

		public void DrawFocusRect(int x, int y, int width, int height)
		{
			RECTAPI gr;
			gr.Top = y;
			gr.Left = x;
			gr.Right = width + x;
			gr.Bottom = height + y;

			NativeUser32Api.DrawFocusRect(hDC, ref gr);
		}

		public void FillRect(Color color, int x, int y, int width, int height)
		{
			GDIBrush b = new GDIBrush(color);
			this.FillRect(b, x, y, width, height);
			b.Dispose();
		}

		public void InvertRect(int x, int y, int width, int height)
		{
			RECTAPI gr;
			gr.Top = y;
			gr.Left = x;
			gr.Right = width + x;
			gr.Bottom = height + y;

			NativeUser32Api.InvertRect(hDC, ref gr);
		}

		public void DrawLine(GDIPen pen, Point p1, Point p2)
		{
			IntPtr oldpen = NativeGdi32Api.SelectObject(hDC, pen.hPen);

			POINTAPI gp;
			gp.X = 0;
			gp.Y = 0;

			NativeGdi32Api.MoveToEx(hDC, p1.X, p1.Y, ref gp);
			NativeGdi32Api.LineTo(hDC, p2.X, p2.Y);

			IntPtr crap = NativeGdi32Api.SelectObject(hDC, oldpen);
		}

		public void DrawLine(Color color, Point p1, Point p2)
		{
			GDIPen p = new GDIPen(color, 1);
			this.DrawLine(p, p1, p2);

			p.Dispose();
		}

		public void DrawRect(Color color, int left, int top, int width, int height)
		{
			GDIPen p = new GDIPen(color, 1);
			this.DrawRect(p, left, top, width, height);

			p.Dispose();
		}

		public void DrawRect(GDIPen pen, int left, int top, int width, int height)
		{
			this.DrawLine(pen, new Point(left, top), new Point(left + width, top));
			this.DrawLine(pen, new Point(left, top + height), new Point(left + width, top + height));
			this.DrawLine(pen, new Point(left, top), new Point(left, top + height));
			this.DrawLine(pen, new Point(left + width, top), new Point(left + width, top + height + 1));
		}

		public void Clear(Color color)
		{
			GDIBrush b = new GDIBrush(color);
			this.Clear(b);

			b.Dispose();
		}

		public void Clear(GDIBrush brush)
		{
			this.FillRect(brush, 0, 0, width, height);
		}

		public void Flush()
		{
			NativeGdi32Api.GdiFlush();
		}

		public void SetBrushOrg(int x, int y)
		{
			POINTAPI p;
			p.X = 0;
			p.Y = 0;

			NativeGdi32Api.SetBrushOrgEx(hDC, x, y, ref p);
		}

		#endregion

		#endregion

		/// <summary>
		/// Initializes a new instance of GDISurface.
		/// </summary>
		public GDISurface(IntPtr hDC)
		{
			this.hDC = hDC;
		}

		/// <summary>
		/// Initializes a new instance of GDISurface.
		/// </summary>
		public GDISurface(int width, int height)
		{
			IntPtr deskDC = NativeUser32Api.GetDC(new IntPtr(0));
			this.Init(width, height, deskDC);
			this.Create();
		}

		/// <summary>
		/// Initializes a new instance of GDISurface.
		/// </summary>
		public GDISurface(int width, int height, IntPtr hdc)
		{
			this.Init(width, height, hdc);
			this.Create();
		}

		/// <summary>
		/// Initializes a new instance of GDISurface.
		/// </summary>
		public GDISurface(int width, int height, GDISurface surface)
		{
			this.Init(width, height, surface.hDC);
			this.Create();
		}

		/// <summary>
		/// Initializes a new instance of GDISurface.
		/// </summary>
		public GDISurface(int width, int height, Control compatibleControl, bool bindControl)
		{
			IntPtr hDCControk = NativeUser32Api.ControlDC(compatibleControl);
			this.Init(width, height, hDCControk);
			NativeUser32Api.ReleaseDC(compatibleControl.Handle, hDCControk);

			if (bindControl)
				this.Control = compatibleControl;

			this.Create();
		}
	}
}
